﻿//
// Copyright 2024 Exhunter Technology Co.Ltd2.
//

using System;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;

namespace X18.Framework.Core.Security.Sm
{
    /// <summary>
    /// SM2加密算法
    /// y2 = x3 + ax + b
    /// </summary>
    public class Sm2
    {
        private static readonly string[] Sm2Param =
        {
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF",
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",
            "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123",
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0"
        };

        private static string[] EccParam = Sm2Param;
        private static readonly BigInteger EccP;
        private static readonly BigInteger EccA;
        private static readonly BigInteger EccB;
        private static readonly BigInteger EccN;
        private static readonly BigInteger EccGx;
        private static readonly BigInteger EccGy;
        private static readonly ECCurve EccCurve;
        private static readonly ECPoint EccPointG;
        private static readonly ECDomainParameters EccBcSpec;
        private static readonly ECKeyPairGenerator EccKeyPairGenerator;

        [Obsolete("Use newMethod instead", false)]
        static Sm2()
        {
            EccParam = Sm2Param;
            EccP = new BigInteger(EccParam[0], 16);
            EccA = new BigInteger(EccParam[1], 16);
            EccB = new BigInteger(EccParam[2], 16);
            EccN = new BigInteger(EccParam[3], 16);
            EccGx = new BigInteger(EccParam[4], 16);
            EccGy = new BigInteger(EccParam[5], 16);
            ECFieldElement x = new FpFieldElement(EccP, EccGx);
            ECFieldElement y = new FpFieldElement(EccP, EccGy);
            EccCurve = new FpCurve(EccP, EccA, EccB);
            EccPointG = new FpPoint(EccCurve, x, y);
            EccBcSpec = new ECDomainParameters(EccCurve, EccPointG, EccN);
            ECKeyGenerationParameters parameters = new ECKeyGenerationParameters(EccBcSpec, new SecureRandom());
            EccKeyPairGenerator = new ECKeyPairGenerator();
            EccKeyPairGenerator.Init(parameters);
        }

        public static KeyPair GetKeyPair()
        {
            AsymmetricCipherKeyPair asymmetricCipherKeyPair = EccKeyPairGenerator.GenerateKeyPair();
            ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters)asymmetricCipherKeyPair.Private;
            ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters)asymmetricCipherKeyPair.Public;
            // 返回
            KeyPair keyPair = new KeyPair();
            keyPair.PublicKey = publicKeyParameters.Q.GetEncoded();
            keyPair.PrivateKey = privateKeyParameters.D.ToByteArray();
            return keyPair;
        }

        /// <summary>
        /// 签名
        /// </summary>
        /// <param name="privateKey">私钥</param>
        /// <param name="data">数据</param>
        /// <returns>签名数据</returns>
        public static byte[] Sign(byte[] privateKey, byte[] data)
        {
            SM2Signer sm2Signer = new SM2Signer();
            BigInteger userKey = new BigInteger(1, privateKey);
            ECPrivateKeyParameters parameters = new ECPrivateKeyParameters(userKey, EccBcSpec);
            sm2Signer.Init(true, parameters);
            sm2Signer.BlockUpdate(data, 0, data.Length);
            return sm2Signer.GenerateSignature();
        }

        /// <summary>
        /// 验签
        /// </summary>
        /// <param name="publicKey">公钥</param>
        /// <param name="data">数据</param>
        /// <param name="sign">签名</param>
        /// <returns></returns>
        public static bool VerifySign(byte[] publicKey, byte[] data, byte[] sign)
        {
            SM2Signer sm2Signer = new SM2Signer();
            ECPoint userKey = EccCurve.DecodePoint(publicKey);
            ECPublicKeyParameters parameters = new ECPublicKeyParameters(userKey, EccBcSpec);
            sm2Signer.Init(false, parameters);
            sm2Signer.BlockUpdate(data, 0, data.Length);
            return sm2Signer.VerifySignature(sign);
        }

        private const int C1Length = 65;
        private const int C3Length = 32;

        /// <summary>
        /// 加密
        /// </summary>
        /// <param name="publicKey">公钥</param>
        /// <param name="data">原文</param>
        /// <returns>密文</returns>
        public static byte[] Encrypt(byte[] publicKey, byte[] data, bool c1c2c3 = false)
        {
            // 加密
            SM2Engine engine = new SM2Engine();
            ECPoint userKey = EccCurve.DecodePoint(publicKey);
            ECPublicKeyParameters parameters = new ECPublicKeyParameters(userKey, EccBcSpec);
            engine.Init(true, new ParametersWithRandom(parameters));
            byte[] buffer = engine.ProcessBlock(data, 0, data.Length);
            if (c1c2c3)
            {
                return buffer;
            }
            else
            {
                // BC加解密使用旧标c1||c2||c3, 将结果转化为c1||c3||c2
                byte[] result = new byte[buffer.Length];
                Array.Copy(buffer, 0, result, 0, C1Length);
                Array.Copy(buffer, buffer.Length - C3Length, result, C1Length, C3Length);
                Array.Copy(buffer, C1Length, result, C1Length + C3Length, buffer.Length - C1Length - C3Length);
                return result;
            }
        }

        /// <summary>
        /// 解密
        /// </summary>
        /// <param name="privateKey">私钥</param>
        /// <param name="data">密文</param>
        /// <returns>明文</returns>
        public static byte[] Decrypt(byte[] privateKey, byte[] data, bool c1c2c3 = false)
        {
            byte[] plain;
            // 准备数据
            if (c1c2c3)
            {
                plain = data;
            }
            else
            {
                plain = new byte[data.Length];
                Array.Copy(data, 0, plain, 0, C1Length);
                Array.Copy(data, C1Length + C3Length, plain, C1Length, data.Length - C1Length - C3Length);
                Array.Copy(data, C1Length, plain, data.Length - C3Length, C3Length);
            }
            // 解密
            SM2Engine engine = new SM2Engine();
            BigInteger userKey = new BigInteger(1, privateKey);
            ECPrivateKeyParameters parameters = new ECPrivateKeyParameters(userKey, EccBcSpec);
            engine.Init(false, parameters);
            return engine.ProcessBlock(plain, 0, plain.Length);
        }
    }
}
